package weka.classifiers.trees.MseTrees;

import java.util.ArrayList;
import java.util.Arrays;

import weka.classifiers.functions.SimpleLinearRegression;
import weka.classifiers.functions.LinearRegression;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;

public class Tree {

	protected double m_totalInstanceWeight;

	/** minimum number of instances at which a node is considered for splitting */
	protected int minNumInstances ;

	/**
	 * Simple regression functions fit by LogitBoost at higher levels in the
	 * tree
	 */
	protected LinearRegression[][] linearRegression;

	protected ArrayList<Tree> childNodes;

	protected ArrayList<Instances> sonsInstances;

	protected double MSE;

	// To DO: extend to nominal attributes
	// To DO: define min Size of MSE ?

	/**
	 * 
	 * @param minNumInstances
	 * @param linearRegression
	 * @param mNumInstances
	 */
	public Tree(Instances data,int minNumInstances ) {
		LinearRegression linearRegression = new LinearRegression();
		this.minNumInstances = minNumInstances;
		buildTree(data, linearRegression);
	}

	public void buildTree(Instances currentData,LinearRegression linearRegressionClassifier) {
		try {
			linearRegressionClassifier.buildClassifier(currentData);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		
		// Stopping condition
		System.out.println("Stopping Condition " + currentData.numInstances() + ">" + minNumInstances);
		
		if (currentData.numInstances() > minNumInstances) {
			Split bestSplit = findBestSplit(currentData,
					linearRegressionClassifier);

			sonsInstances = splitByAttribute(currentData,
					bestSplit.getSplittingAttribute(),
					bestSplit.getSplittingInstance());

			for (int i = 1; i < sonsInstances.size(); i++) {
				// Recursive call
				Tree childNode = new Tree(sonsInstances.get(i),minNumInstances);
				childNodes.add(childNode);
			}
		}else{
			System.out.println("Exit Recursion");
		}
			
	}

	private ArrayList<Instances> splitByAttribute(Instances currentData,
			int splittingAttribute, int splittingInstance) {
		Instances LeftNode = new Instances(currentData,
				currentData.numInstances());
		Instances RightNode = new Instances(currentData,
				currentData.numInstances());
		ArrayList<Instances> sonsInstances = new ArrayList<Instances>();

		for (int i = 0; i < currentData.numInstances(); i++) {
			Instance inst = currentData.instance(i);
			if (inst.value(splittingAttribute) > currentData.instance(
					splittingInstance).value(splittingAttribute)) {
				LeftNode.add(inst);
			} else {
				RightNode.add(inst);
			}
		}
		sonsInstances.add(LeftNode);
		sonsInstances.add(RightNode);
		return sonsInstances;
	}

	private Split findBestSplit(Instances data,
			LinearRegression linearRegressionClassifier) {

		double bestMse = 0;
		int bestAttributeToSplitBy = 0;
		int bestInstanceToSplitBy = 0;
		Split bestSplit = new Split();

		for (int j = 0; j < data.numAttributes(); j++) {
			Attribute splittingAttribute = data.attribute(j);

			// Actual Values of the current attribute
			double[] attributeInstanceValues = new double[data.numInstances()];

			for (int i = 0; i < data.numInstances(); i++) {
				Instance inst = data.instance(i);
				attributeInstanceValues[i] = inst.value(j);

				ArrayList<Instance> LeftBranch = new ArrayList<Instance>();
				ArrayList<Instance> RightBranch = new ArrayList<Instance>();
				for (double instValue : attributeInstanceValues) {
					if (instValue > inst.value(j)) {
						LeftBranch.add(inst);
					} else {
						RightBranch.add(inst);
					}
				}

				double leftMSE = calculateMSE(LeftBranch,
						linearRegressionClassifier, j);

				double rightMSE = calculateMSE(RightBranch,
						linearRegressionClassifier, j);
				double mse = leftMSE + rightMSE;
				
				if ((mse < bestMse) || 
					(bestMse == 0)) {
					bestMse = mse;
					System.out.println("leftmse=" + leftMSE + " rightmse=" + rightMSE + " bestAttr=" + bestAttributeToSplitBy );
					bestAttributeToSplitBy = j;
					bestInstanceToSplitBy = i;
				}
			}
		}
		bestSplit.setSplittingAttribute(bestAttributeToSplitBy);
		bestSplit.setSplittingInstance(bestInstanceToSplitBy);
		return bestSplit;
	}

	private double calculateMSE(ArrayList<Instance> subsetInstances,
			LinearRegression LinearRegressionClassifier, int numAttributes) {
		//System.out.println("   calculateMSE for aat="+numAttributes);
		double error		= 0;
		double squaredError	= 0;
		double predicted 	= 0;
		double actual 		= 0;
		double sse 			= 0;
		double mse			= 0;
		try {
		for (Instance inst : subsetInstances) {
			
			
			predicted = LinearRegressionClassifier.classifyInstance(inst);
			actual = inst.value(numAttributes);
			error = actual - predicted;
			squaredError = error * error;
			sse =+ squaredError;
			
			//System.out.println("	real value=" + actual + " predicted="+predicted + " error="+ error + " squaredError=" + squaredError + " sse="+sse);
		}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		mse = sse/subsetInstances.size();
		//System.out.println("		mse="+mse);
		return mse;
	}

}
